3.7.ACL权限管理(zero-rbac)

白皮书中的权限管理主要包括两部分:管理端和消费端

  • 管理端:可扩展面板式架构,现阶段版本支持 菜单管理、流程定制 两类,您可以扩展自己所需的不同权限管理的结构。

  • 消费端:XBAC模型:基于角色的RBAC(Role)、基于属性的ABAC(Attribute)、基于策略的PBAC(Policy)。

Zero在线教程中讲解过可插拔模式的认证组件,此处略过认证部分(@Wall ),本书主要讲解扩展模块中的授权以及 zero-rbac 模块如何实现企业级需求的复杂授权流程,其中标准化认证采用了 OAuth 规范。

3.7.1.消费端

3.7.1.1.XBAC模型

Zero中的基本权限主体模型如下:

zarch perm

上图中的核心概念如下:

实体表 概念 含义

S_USER

用户

登录系统专用账号信息表。

S_ROLE

角色

和账号关联的角色信息表。

S_GROUP

用户组

和账号关联的用户组信息表。

S_PERMISSION

权限

和角色相关联的权限记录表。

S_PERM_SET

权限集

服务于权限定义的权限集合表,主要用于管理段构造权限集合实现批量授权。

S_ACTION

操作

隶属于权限记录的操作集合,操作最终会绑定到对应的操作主体上。

  • 所有关联表(R_ 前缀)目前都是多对多结构,一个用户可关联多个角色,一个用户可关联多个用户组,一个用户组关联多个角色,一个角色关联多个权限记录,一个权限记录包含多个 安全操作

  • 用户组支持继承结构,包括父组和子组的概念,权限计算过程中,不同算法在父子级结构中发挥的作用会有所区别。

  • 角色和用户组关联过程中支持优先级,即用户在关联角色和用户组时存在 第一角色、第一用户组 的概念。

常用的基本配置中,只要资源所需操作级别和基础算法模型计算的最终权限路径和用户所拥有的权限路径是通的,那么就可以实现基础权限的认证,这部分内容在完成本章节所有讲解之后会有更深入的说明。

安全操作 S_ACTION 对端关联到资源表 S_RESOURCE 和资源形成1对1的绑定,资源中定义的就是访问资源的基本要求,而S_ACTION中计算出来的结果就是登录账号所拥有的资源访问资格,当资源访问资格满足了资源基本要求时就认证通过,证明当前账号有权限访问该资源。但是、但是、但是——此处只是解决了账号:能不能 的问题,在资源访问之后还会有两张专用的数据表来执行 访问多少 的问题,这是Zero中数据域的实现原理,在后续的 N维安全视图 中加以说明。

3.7.1.2.多态身份(Profile)

Zero中由于复杂的多对多结构,最终会形成不同的多态身份(Profile),S_RESOURCE 表中有如下字段对资源访问资格执行定义:

字段名 含义 取值

MODE_ROLE

按角色查找资源的模式

UNION、EAGER、LAZY、INTERSECT

MODE_GROUP

按用户组查找资源的模式

HORIZON、CRITICAL、OVERLOOK

MODE_TREE

用户组继承和非继承树模式查找

EXTEND、PARENT、CHILD、INHERIT

上述取值是资源对多态身份的定义,最终形成的多态身份 Profile 的值列表如下(举例定义角色和组的优先级):

  • 多态身份只有在多角色、关联用户组、多用户组模式下生效,如果只包含一个角色,多态身份会演变成最简单的 USER_UNION 模式。

  • 用户/角色、用户组/角色在计算时都支持 UNION, EAGER, LAZY, INTERSECT 四种模式。

  • 使用用户组模式时,只要计算出选择使用什么用户组,就可以执行用户组对应的角色关联权限集的计算。

不含组模式

假设用户包含如下信息,这种模式(高频使用模式)下 MODE_GROUP / MODE_TREE 都设置成 NULL

R1(H):P1、P2、P3,高优先级角色,包含三条权限记录。
R2(L):P2、P4,低优先级角色,包含两条权限记录。

zero p user

含义

USER_UNION

zero p user union 并集模式,最终权限集为 P1, P2, P3, P4

USER_EAGER

zero p user eager 高优先级模式,使用最高优先级角色的权限集,此处:P1, P2, P3

USER_LAZY

zero p user lazy 低优先级模式,使用最低优先级角色的权限集,此处:P2, P4

USER_INTERSECT

zero p user intersect 交集模式,最终权限集为 P2

用户组模式

假设用户包含如下信息:

用户组结构如下(此处不列举组所对应的权限集):
         G10
        /   \
      G20   G21
     /   \    \
    G30  G31  G32
而登录用户只包含如下三个用户组:
G20(H):高优先级用户组
G31(M):中优先级用户组
G32(L):低优先级用户组
全量优先级:
G10 > G20 > G21 > G30 > G31 > G32

zero p group

组计算 角色计算 计算流程

HORIZON_UNION

zero p group u

zero p user union

UNION模式计算单个用户组关联角色权限集,再将三个组的权限集合并。

HORIZON_EAGER

zero p group u

zero p user eager

EAGER模式计算单个用户组关联角色权限集,再将三个组的权限集合并。

HORIZON_LAZY

zero p group u

zero p user lazy

LAZY模式计算单个用户组关联角色权限集,再将三个组的权限集合并。

HORIZON_INTERSECT

zero p group u

zero p user intersect

INTERSECT模式计算单个用户组关联角色权限集,再将三个组的权限集合并。

CRITICAL_UNION

zero p group h

zero p user union

UNION模式计算高优先级组的权限集。

CRITICAL_EAGER

zero p group h

zero p user eager

EAGER模式计算高优先级组的权限集。

CRITICAL_LAZY

zero p group h

zero p user lazy

LAZY模式计算高优先级组的权限集。

CRITICAL_INTERSECT

zero p group h

zero p user intersect

INTERSECT模式计算高优先级的权限集。

OVERLOOK_UNION

zero p group l

zero p user union

UNION模式计算低优先级组的权限集。

OVERLOOK_EAGER

zero p group l

zero p user eager

EAGER模式计算低优先级组的权限集。

OVERLOOK_LAZY

zero p group l

zero p user lazy

LAZY模式计算低优先级组的权限集。

OVERLOOK_INTERSECT

zero p group l

zero p user intersect

INTERSECT模式计算低优先级组的权限集。

PARENT_HORIZON_UNION

zero p parent gu

zero p user union

先查找三个组的父组,再按UNION模式计算权限集。

PARENT_HORIZON_EAGER

zero p parent gu

zero p user eager

先查找三个组的父组,再按EAGER模式计算权限集。

PARENT_HORIZON_LAZY

zero p parent gu

zero p user lazy

先查找三个组的父组,再按LAZY模式计算权限集。

PARENT_HORIZON_INTERSECT

zero p parent gu

zero p user intersect

先查找三个组的父组,再按INTERSECT模式计算权限集。

PARENT_CRITICAL_UNION

zero p parent gh

zero p user union

查找优先级最高组的父组,再按UNION模式计算权限集。

PARENT_CRITICAL_EAGER

zero p parent gh

zero p user eager

查找优先级最高组的父组,再按EAGER模式计算权限集。

PARENT_CRITICAL_LAZY

zero p parent gh

zero p user lazy

查找优先级最高组的父组,再按LAZY模式计算权限集。

PARENT_CRITICAL_INTERSECT

zero p parent gh

zero p user intersect

查找优先级最高组的父组,再按INTERSECT模式计算权限集。

PARENT_OVERLOOK_UNION

zero p parent gl

zero p user union

查找优先级最低组的父组,再按UNION模式计算权限集。

PARENT_OVERLOOK_EAGER

zero p parent gl

zero p user eager

查找优先级最低组的父组,再按EAGER模式计算权限集。

PARENT_OVERLOOK_LAZY

zero p parent gl

zero p user lazy

查找优先级最低组的父组,再按LAZY模式计算权限集。

PARENT_OVERLOOK_INTERSECT

zero p parent gl

zero p user intersect

查找优先级最低组的父组,再按INTERSECT模式计算权限集。

CHILD_HORIZON_UNION

zero p child gu

zero p user union

查找所有组的子组,再按UNION模式计算权限集。

CHILD_HORIZON_EAGER

zero p child gu

zero p user eager

查找所有组的子组,再按EAGER模式计算权限集。

CHILD_HORIZON_LAZY

zero p child gu

zero p user lazy

查找所有组的子组,再按LAZY模式计算权限集。

CHILD_HORIZON_INTERSECT

zero p child gu

zero p user intersect

查找所有组的子组,再按INTERSECT模式计算权限集。

CHILD_CRITICAL_UNION

zero p child gu

zero p user union

查找优先级最高组的子组,再按UNION模式计算权限集。

CHILD_CRITICAL_EAGER

zero p child gu

zero p user eager

查找优先级最高组的子组,再按EAGER模式计算权限集。

CHILD_CRITICAL_LAZY

zero p child gu

zero p user lazy

查找优先级最高组的子组,再按LAZY模式计算权限集。

CHILD_CRITICAL_INTERSECT

zero p child gu

zero p user intersect

查找优先级最高组的子组,再按INTERSECT模式计算权限集。

CHILD_OVERLOOK_UNION

zero p child gl

zero p user union

(无权限)查找优先级最低组的子组,再按UNION模式计算权限集。

CHILD_OVERLOOK_EAGER

zero p child gl

zero p user eager

(无权限)查找优先级最低组的子组,再按EAGER模式计算权限集。

CHILD_OVERLOOK_LAZY

zero p child gl

zero p user lazy

(无权限)查找优先级最低组的子组,再按LAZY模式计算权限集。

CHILD_OVERLOOK_INTERSECT

zero p child gl

zero p user intersect

(无权限)查找优先级最低组的子组,再按INTERSECT模式计算权限集。

INHERIT_HORIZON_UNION

zero p inherit gu

zero p user union

查找所有组父组包含本组,再按UNION模式计算权限集。

INHERIT_HORIZON_EAGER

zero p inherit gu

zero p user eager

查找所有组父组包含本组,再按EAGER模式计算权限集。

INHERIT_HORIZON_LAZY

zero p inherit gu

zero p user lazy

查找所有组父组包含本组,再按LAZY模式计算权限集。

INHERIT_HORIZON_INTERSECT

zero p inherit gu

zero p user intersect

查找所有组父组包含本组,再按INTERSECT模式计算权限集。

INHERIT_CRITICAL_UNION

zero p inherit gh

zero p user union

查找优先级高组的父组包含本组,再按UNION模式计算权限集。

INHERIT_CRITICAL_EAGER

zero p inherit gh

zero p user eager

查找优先级高组的父组包含本组,再按EAGER模式计算权限集。

INHERIT_CRITICAL_LAZY

zero p inherit gh

zero p user lazy

查找优先级高组的父组包含本组,再按LAZY模式计算权限集。

INHERIT_CRITICAL_INTERSECT

zero p inherit gh

zero p user intersect

查找优先级高组的父组包含本组,再按INTERSECT模式计算权限集。

INHERIT_OVERLOOK_UNION

zero p inherit gl

zero p user union

查找优先级低组的父组包含本组,再按UNION模式计算权限集。

INHERIT_OVERLOOK_EAGER

zero p inherit gl

zero p user eager

查找优先级低组的父组包含本组,再按EAGER模式计算权限集。

INHERIT_OVERLOOK_LAZY

zero p inherit gl

zero p user lazy

查找优先级低组的父组包含本组,再按LAZY模式计算权限集。

INHERIT_OVERLOOK_INTERSECT

zero p inherit gl

zero p user intersect

查找优先级低组的父组包含本组,再按INTERSECT模式计算权限集。

EXTEND_HORIZON_UNION

zero p extend gu

zero p user union

查找所有组子组包含本组,再按UNION模式计算权限集。

EXTEND_HORIZON_EAGER

zero p extend gu

zero p user eager

查找所有组子组包含本组,再按EAGER模式计算权限集。

EXTEND_HORIZON_LAZY

zero p extend gu

zero p user lazy

查找所有组子组包含本组,再按LAZY模式计算权限集。

EXTEND_HORIZON_INTERSECT

zero p extend gu

zero p user intersect

查找所有组子组包含本组,再按INTERSECT模式计算权限集。

EXTEND_CRITICAL_UNION

zero p extend gh

zero p user union

查找优先级高组的子组包含本组,再按UNION模式计算权限集。

EXTEND_CRITICAL_EAGER

zero p extend gh

zero p user eager

查找优先级高组的子组包含本组,再按EAGER模式计算权限集。

EXTEND_CRITICAL_LAZY

zero p extend gh

zero p user lazy

查找优先级高组的子组包含本组,再按LAZY模式计算权限集。

EXTEND_CRITICAL_INTERSECT

zero p extend gh

zero p user intersect

查找优先级高组的子组包含本组,再按INTERSECT模式计算权限集。

EXTEND_OVERLOOK_UNION

zero p extend gl

zero p user union

查找优先级低组的子组包含本组,再按UNION模式计算权限集。

EXTEND_OVERLOOK_EAGER

zero p extend gl

zero p user eager

查找优先级低组的子组包含本组,再按EAGER模式计算权限集。

EXTEND_OVERLOOK_LAZY

zero p extend gl

zero p user lazy

查找优先级低组的子组包含本组,再按LAZY模式计算权限集。

EXTEND_OVERLOOK_INTERSECT

zero p extend gl

zero p user intersect

查找优先级低组的子组包含本组,再按INTERSECT模式计算权限集。

多态身份Profile是整个 Zero权限框架中的一个 过度设计 的典范,从实际场景看起来真正使用到这部分的内容仅局限于 USER_X 四种计算模式。但根据设计时的调研和考察,有这部分功能之后,对于复杂组织架构之间的权限控制可以达到非常细粒度的级别,并且在变化过程中,可实现更多变化模式下的权限控制。

对用户而言,一旦登录之后,自己的 Profile 就已经固定,而资源需求要求的Profile则不一定固定,属于变量,最终计算结果近似于查找最短路径,达到用户组这个级别的额外的变化模式(包括继承、包括派生、包括限制、包括组合等),最终 Zero权限框架中合计支持64种Profile配置,如此就解决了资源 能不能 访问的问题。

3.7.1.3.N维安全视图(View)

前文解决了资源 能不能 访问的问题,本章就在可访问的基础上解决 访问多少 的问题,Zero权限框架中的 S_RESOURCES_ACTION 是强绑定关系,它们之间只会单纯对比操作级别和资源需求级别是否可访问,一旦访问成功,就会衍生计算读写操作的边界,在Zero权限框架中读写边界的划定取决于安全视图 S_VIEW 中的定义。

安全视图基础

安全视图的基础维度如下:

字段名 含义 取值

NAME

视图名称

默认取值 DEFAULT。

POSITION

视图位置

默认取值 DEFAULT。

OWNER_TYPE

视图所属者类型

只包含两种:ROLE-角色视图,USER-用户视图。

OWNER

视图所属者ID

如果是角色视图则是角色ID,如果是用户视图则是用户ID。

RESOURCE_ID

视图所属资源

当前视图关联的 S_RESOURCE 资源ID,一个资源可能存在多个安全视图。

POSITION 和 NAME 构造的视图的核心维度,在系统出现不同需求时会起重要作用:

场景 NAME POSITION

单模块无视图管理

DEFAULT

DEFAULT

单模块带视图

?

DEFAULT

多模块无视图管理

DEFAULT

?

多模块多视图管理

?

?

此处解释一下模块的概念,此处的 模块 并非我们开发过程中的模块,此处的模块底层关联模型只有一个,而模块更多是从列表作为入口。例如:

  • 单纯的CRUD应用(角色管理),管理过程中它的模块只有一个,从角色列表进入,然后管理,一般场景不会开第二模块进行特殊管理。

  • 带分类的CRUD应用(订单管理),可能带有不同列表管理处理,如正在执行的订单模块、已经完成的订单模块,而此时后台关联的模型依旧是 订单,这种情况下 POSITION 就显得很重要。

而且 POSITION 会比资源多一个维度,通常资源是后接口绑定,如 /api/xxx/search 的资源接口,但这个资源接口由于支持查询引擎语法,可能应用于不同的菜单入口(上述提到的正在执行的订单、已完成的订单)等,这种情况下两个菜单共享了一个资源,而为了针对不同的菜单定义 角色视图/用户视图,最好的方式就是启用 POSITION 参数。如此计算下来,POSITION既不和查询条件绑定(不同页面、不同位置、同一查询条件),也不可以和页面绑定,如果出现 TAB 页签会造成同一个页面中出现两种不同的查询(可能是两种不同的 POSITION),最终它只能和列表的配置绑定,直接在前端中提供它的配置来完成和列表绑定的过程,这一块的用法属于 Zero权限框架中的难点,其应用范围十分广泛,现阶段通常使用场景如下:

  • 按类型划分位置信息:分类字段管理 /ambient/tabular/:type 不同页面取不同的 POSITION,实现抽象态的列表管理,这种思路同样适用于:档案、合同、项目、员工、客户、分类等。

  • 按类型划分位置信息:如待办列表和已办列表,最终访问资源可能都是 /api/todo/search,而由于状态不同,所以设置不同的 POSITION 实现视图的定制。

  • 按流程划分QBE:目前系统中流程右上角的QBE列表页是基于此种逻辑,几乎不使用开发的模式就定制完成。

参考下图的结构:

zero p view

上图结构可以看到 POSITIONNAME(在模块访问中通常使用 VIEW)的使用场景会有所差异:

  • POSITION主要用于模块维度的拓展,它的起点是模块。

  • VIEW主要用于视图拥有者维度的拓展,它的起点是拥有者,如角色、用户、目录级(实验版本)。

安全视图类

Zero中存在一个特殊的参数对象:

    public class Vis extends JsonObject

    // 该参数对象使用时可如下:
    @POST
    @Path("/{actor}/search")
    @Address(Addr.Post.SEARCH)
    @Adjust(Orders.MODULE)
    JsonObject search(@PathParam("actor") String actor,
                      @BodyParam JsonObject data,
                      @QueryParam(KName.MODULE) String module,
                      @PointParam(KName.VIEW) Vis view);

该参数的格式比较特殊,通常使用的是 [view,position] 的数据格式,也是此处的 @PointParam 注解解析的内容,它可以将上述格式直接解析成视图的两个核心维度( NAME, POSITION ),并将该维度应用于任意支持它的接口。Vis类中存在一个特殊方法smart会对视图数据格式做智能解析,它支持的几种格式如下:

  • Vis类型:如果传入的类型是Vis类型,则直接做引用赋值。

  • JsonObject类型:如果传入的类型是JsonObject类型,则解析格式:{"position": "xxx","view":"xxx"}

  • JsonArray类型:如果传如的类型是JsonArray类型,则解析格式:`[view,position]`执行解析。

  • String类型:如果传如类型是String类型,除了完成URL的 decode 流程之外:

    • 如果String类型是JsonArray格式则做一次强制转换,执行JsonObject类型解析。

    • 如果String类型是JsonArray格式则做一次强制转换,执行JsonArray类型解析。

    • 否则String类型参数直接作为视图名称看待,而赋 POSITION 为默认值 DEFAULT

  • 默认创建专用默认视图:view = DEFAULT, position = DEFAULT

窗口定义

安全视图的窗口定义主要依靠下边几个字段:

字段名 含义

PROJECTION

JsonArray格式,执行该视图的列过滤,直接过滤掉接口返回数据的列信息。

CRITERIA

JsonObject格式,执行该视图默认的 Qr 语法注入,启用查询引擎追加接口的查询条件语法。

ROWS

JsonObject格式,针对行数据执行筛选,生成 IN 语句筛选特定行,通常是查询引擎无法做细粒度筛选时的一种折中选择。

VISITANT

布尔值,是否启用 虚拟视图(资源访问者)

  • PROJECTION 会作用于不同类型的前端组件,通常用于 LIST/FORM 两种,Zero框架中的保存列表的列信息以及表单中针对部分表单执行字段过滤就依赖它来完成,它是后置过滤(实际会从数据库中查询出所有信息进行值提取,现阶段没有明显的性能问题)。

  • CRITERIA 主要针对于查询,它会隐式修改查询引擎的 Qr 语法,导致前端发送查询条件在安全视图作用下被直接修改,如果用户中出现了多个角色、多个用户组,则按照最终资源需求中定义的 Profile 来完成查询条件的拼合,默认模式下多个角色之间使用 OR 连接符。

  • ROWS 针对特殊资源提取,提供基于主键的直接命中条件,解决异构查询模式下用户无法使用Join的情况,由于表单是单条数据记录,所以一般表单接口无法支持该属性(设置了也没有效果);通常此属性作用于列表:

    • DATA:在数据层面,列表处理过程中直接针对条件执行过滤,典型应用为:菜单筛选、字典筛选、分类树筛选。

    • META:元数据层面,处理过程中只读取ROWS中设置过的的信息(特殊模式下载界面呈现模式出现ReadOnly时,它的定义位于UI配置中,而不是安全视图层。

虚拟视图(资源访问者)部分参考下一个章节的详细说明,上述限制中,虽然 ROWS 无法直接作用于表单属性级的过滤窗口操作,但是它可以实现动态表单(/api/ui/form)模式下的属性集过滤,比如读取表单的属性有25个,使用 `ROWS`直接过滤掉一部分留下一部分,最终输出的是配置、配置、配置,最终表单引擎根据配置渲染表单,可以完成间接作用流程。

视图检索流程

看完了上述安全视图的方方面面之后,视图检索流程就变得很简单了(后端会根据访问资源键值生成 session-<METHOD>:<URI>:<POSITION>/<VIEW> 格式的视图缓存键)。下边是用户访问某个资源接口时的详细流程:

  1. 用户发送请求到某个资源接口如:/api/xxx/resource

  2. 系统检索该资源是否存在用户级的 S_VIEW 记录(OWNER_TYPE = USER, OWNER = <USER_ID>),如果存在该记录,则直接提取安全视图记录对资源执行前后(BEFORE/AFTER)计算。

    • BEFORE计算:BEFORE计算的核心算法主要在于修改参数,它的性能更高,一般直接作用于 criteria 参数部分实现查询条件的注入流程。

    • AFTER计算:AFTER计算会完整访问数据库,在查询出来的结果集中做运算,虽然性能会有损耗,但在某些场景下(需要全量元数据)是必须要走AFTER的。

  3. 若不存在用户级的 S_VIEW 记录,则继续检索是否有角色级的 S_VIEW 记录,若存在则计算。

  4. 上述两步都不存在时,忽略安全视图,可访问资源内所有内容。

从上述流程可以知道,用户级安全视图优先级比角色高,一般用户级安全视图都是个人视图模式存在,比如某个模块的视图管理,而角色级的视图都是管理员预设,单个用户不可以更改,比如管理员直接针对财务人员以外的角色设置不可访问某些资源的固定列如薪资、账期等。

最终 访问多少 的问题就直接被安全视图处理掉了,不同角色不同用户在此框架之下访问同一个接口时返回数据就可能出现不同,那么这样就解决了资源重用并且 访问多少 的问题。 :data-uri:

3.7.1.4.资源访问者(Visitor)

多用于抽象层次比较高的 动态建模 领域的权限控制。

本章进入Zero权限框架中的一个新的领域:虚拟资源/资源访问者,在讲虚拟资源之前先思考:为什么要使用虚拟资源?前文安全视图中的定义不知道读者是否发现一个小问题:静态的——一旦绑定了资源之后,就只能在某个资源中直接设置参数和条件,而这里设置的所有条件以及参数都 不依赖输入,您考虑下边一个场景。

我在后端书写了一个接口:/api/xxx/:type,这个接口在后端的定义的接口和 S_RESOURCE 记录仅有一条(为什么,除非您直接愿意写成 /api/xxx/type1/api/xxx/type2 两个接口),这种场景下,意味着如果`type`有三个值,那么我所期望的安全视图应该有三种,根据前文提到的,您可以设置 S_VIEWPOSITION 来限定资源视图,就完成了三种模式下的定义。但若现在我的 :type 参数不是三种,可能存在N种或者上百种,如何解决?Zero为了解决这种安全视图方案,提供了 资源访问者 的概念。资源访问者存储于后端配置表 S_VISITANT 中,一个 S_VIEW(严格说是 VISITANT = true 的视图记录)可能包含无数个资源访问者。

zero p visit arch

从上图可以知道在动态建模过程中,路由器防火墙 可能意味着不同模型,而且应用程序会不断添加新的设备类型,产生新的模型,这种模式下使用 POSITION 扩展会造成大量冗余,况且此时的入口是虚拟资源,即意味着:您在访问该资源时,您是不知道您访问的资源究竟是路由器还是防火墙,那么这样的流程中,就只能通过 访问者 语法计算出真实访问者,然后根据资源访问者去定位真实的资源。从这点意义上讲您可以理解两点:

  • POSITION:先知模式,在访问资源之前,您已经知道您要访问的维度数据是什么,资源访问入口是具象层。

  • VISITANT:后知模式,在访问资源之前,您不知道您要访问的维度数据是什么,资源访问入口是抽象层(这也是虚拟资源一词的来源)。

Zero中按照如下流程配置一个资源访问者:

  1. 资源本身(S_RESOURCE)将该资源定义成一个虚拟资源(VIRTUAL = true)。

  2. 在(S_RESOURCE)资源字段中定义访问者基本规则:

    • 设置访问者语法 SEEK_SYNTAX 字段。

    • 设置访问者配置 SEEK_CONFIG 字段。

    • 设置访问者组件 SEEK_COMPONENT 字段(Java类名)。

  3. 用户发送请求过来时会读取 S_VIEW 的视图信息,一旦读取到视图信息后,对 VISITANT = true执行校验,校验成功之后执行资源访问者流程。

资源访问者流程可以总结成两个大步骤:

步骤 说明

真实资源查找

根据 S_RESOURCE 中的 VIRTUAL = true 的定义去查找真实资源,真实资源的查找就借用访问者配置(前文提到的基本规则)来实现。

访问者安全视图

工具 S_VIEW 中的 VISITANT = true 的定义去同步查找真实资源模式下的安全视图,该视图比 S_VIEW 中扩展的属性更多,渗透到表单级、操作级、非接口级,数据更强大的ACL控制视图逻辑。

访问者逻辑

资源访问者的内部逻辑流程如下:

  1. 检查访问视图 S_VIEW 是否一个带有访问者的安全视图(VISITANT = true)的定义。

  2. 根据资源 S_RESOURCE 中的访问者定义(seekSyntax / seekConfig / seekComponent)计算真实资源访问规则。

  3. 扫描资源访问者的 模式(Replace/Extend)和 作用阶段(EAGER/DELAY),并执行访问者操作。

  4. 读取访问者相关信息,将这些信息和 S_VIEW 中的信息合并计算,计算访问者安全窗口。

关于访问者语法结构定义在此多做点说明,在实际场景中,通常资源的抽象模式(访问者维度)都只有一个维度,所以访问者语法也只是针对资源的访问做一个维度层面的拓展,若您的应用中真的出现了 超级接口,访问者维度需要通过多维模式去实现时,您可以自定义扩展:

  • 单维度标准化:`seekSyntax`定义可完成。

  • 多维度标准化:`seekSyntax`新版多维访问者定义完成。

  • 多维度扩展:`seekConfig/seekComponent`自定义完成。

个人不推荐在系统中做过多的 超级接口 ——一个接口完成多种不同的逻辑,这样的方式运维将会造成一定的压力,并且不容易拓展,但在动态建模和动态接口部分,往往会牵涉到模型的变体,变体出现时这种模式的维度是必须存在的,所以就只能依赖扩展配置来完成。

多说一句:Zero扩展框架中存在很多 config/component 的架构,通常 component 是一个实现了固定接口的Java类,而 config 则是和该类配套的配置数据(Json格式),这样的格式可以让任何开发人员自由发挥拓展自己的应用,访问者逻辑部分也是如此。

访问者语法 存在的目的是查找视图中对应的访问者信息,由于一个视图可能存在多个访问者,所以系统必须保证本次请求的访问者信息——要么只有1个,要么不存在;否则系统会出现二义性问题导致最终无法定位使用哪个资源访问者来处理请求,所以目前Zero权限框架的版本中,访问者主键 S_VISITANT 表中的 SEEK_KEY 字段是唯一的,而访问者语法的最终目的就是计算 SEEK_KEY 生成访问者查询条件。

(执行维度)模式和阶段

资源访问者语法中的模式 mode 信息( S_VISITANT 表中的 MODE 字段):

  • Replace:替换模式,这种模式下,访问者视图会直接覆盖 S_VIEW 中的安全窗口规则,也就是说资源访问不再遵循 S_VIEW 中的安全规则,而直接使用访问者规则。

  • Extend:扩展模式,这种模式下,访问者视图会和 S_VIEW 中的安全窗口规则合并计算,形成新的组合好的访问者规则。

它的作用如下:

zero p visit mode

资源访问者语法中的阶段 phase 信息( S_VISITANT 表中的 PHASE 字段):

  • EAGER:通常当前资源立即生效,一般执行数据读取时会使用 EAGER 阶段(就在当前接口生效)。

  • DELAY:这种阶段通常是读取配置项作用于子资源或其他资源时生效,一般读取元数据和配置数据时使用 DELAY 阶段(DELAY阶段 DataRegion 中的视图模式依旧生效)。

它的作用如下:

zero p visit phase

对于直接开发的接口,基本上EAGER模式就可以满足大量的接口安全控制需求,您不需要开发额外的逻辑就实现了安全视图的控制,但对于比较特殊的基于配置的流程中,通常接口会分为:元数据接口和数据接口两部分,二者相互影响并相互作用,这样的条件下,资源访问者就体现出它的价值了,一个访问者就解决了元数据接口和数据接口的双重安全作用,而不需要依赖每个位置都定义对应的接口。

(定义维度)类型/标识/唯一键

访问者记录中虽然保存了 SEEK_KEY,但在不同的业务场景中,它无法做 全局标识,所以根据实际需求,此处设计在后期做过一些基本改动,访问者标识维度如下:

字段 含义

TYPE

访问者类型,针对资源维度的分类定义,现阶段支持:FORM、LIST、OP、VIEW 四种,服务于 zero-atom 动态建模。

IDENTIFIER

模型标识符,针对模型维度的分类定义,主要提供master模型的基础访问者(限定于所有场景)。

SEEK_KEY

访问者主键,系统可以根据访问者主键查询访问者信息。

访问者标识维度从执行和定义两个方向处理:

  1. 定义维度:VIEW_ID, TYPE, IDENTIFIER 形成唯一键。

  2. 执行维度:VIEW_ID, TYPE, SEEK_KEY 形成唯一键。

举个例子,在读取表单配置时,表单的配置可直接使用:`VIEW_ID,TYPE,SEEK_KEY`限定:

  • VIEW_ID :标识了读取表单的资源接口信息。

  • TYPE :手动定义,静态模式下使用固定值,动态模式中则可以直接使用限定值。

  • SEEK_KEY :根据表单计算这种类型的主键,此时它的值可以是表单的ID(动态),也可以是表单的CODE(静态)。

这样处理之后就实现了表单资源的唯一访问者读取。

SEEK_KEY 目前最常用的两种场景如:

  1. 动态建模过程中,资源访问会牵涉 controlId(表单、列表的组件ID),它的格式通常如下:

    {
        "type": "LIST / FORM / OP",
        "controlId": "UI_CONTROL表中定义的组件的ID,由于UI_CONTROL中已经定义了模型标识符,所以此处模型标识符则可直接省略。"
    }
  2. 静态建模过程中,资源访问者会牵涉到其他内容来构造 SEEK_KEY,如:

    {
        "type": "LIST / FORM /OP",
        "workflow": "工作流名称"
    }

现在版本中的流程 SEEK_KEY 的值格式通常是::workflow/:event/DEFAULT ,其中包含了流程名称和任务名称,这样就可以保证流程中的每个节点都可设置访问者,配合 TYPE 字段实现列表、表单、操作不同级别的访问。

访问控制

访问者对列表的控制基本和 S_VIEW 同源,主要覆盖如下:

字段 含义

DM_ROW

等价于视图中的 ROWS 限定规则,前端标记:H - Horizon,水平限定。

DM_QR

等价于视图中的 CRITERIA 限定规则,前端标记:Q - Query,查询限定。

DM_COLUMN

等价于视图中的 PROJECTION 限定规则,前端标记:V - Vertical,垂直限定。

访问者优于视图的地方在于它还提供了表单级的控制,主要覆盖如下:

字段 关键字 含义

ACL_VISIBLE

可见性

可见属性集设置,限定表单中的某些表单字段是否可见。

ACL_VIEW

只读

只读属性集设置,可编辑依靠计算:可见性 - 只读

*ACL_VARIETY

多样性

多样性属性集设置,用于控制集合类型的属性变体,如数组类的多样性属性集,递归三种属性集,标记某个子属性配置。

ACL_VOW

引用

针对引用类型的属性进行限定,比如订单中关联了员工信息,而员工信息可直接被订单接口的访问者执行表单级属性过滤。

ACL_VERGE

依赖

保存了所有依赖属性的相关信息,和引用方向相反。

上述属性中 ACL_VARIETY 属于特殊访问属性,通常可针对列表、集合等不同的访问属性进行内部数据结构的限定,有了这些限定之后,不同的用户在访问同一张表单可达到如下权限控制效果:

  • 访问的表单长相不同(可见性处理)。

  • 访问同样的表单部分属性看见的内容不同,包括只读、可编辑。

  • 表单引入:有值不可看的情况,这种情况下表单中会显示 ** 来处理(仅针对特殊角色和用户生效)。

  • 依赖属性的不可见处理,所有和表单关联的数据出现不可看的情况。

3.7.1.5.配置步骤

到这里,所有和Zero权限框架相关的内容就已经讲完了,本章节主要讲解配置步骤,开发人员可参考此章节配置上述所有内容。

1. 权限基础配置

权限基础配置在于配置用户是否有权限访问某个资源。

  1. 根据:S_USER / S_ROLE / S_GROUP / S_PERMISSION 设置账号到权限的路径数据信息。

  2. 开发自定义接口如:/api/xxx/order,并且将此资源接口信息录入到 S_RESOURCE 表和 S_ACTION 表中,并将 S_ACTION 记录挂在某个 S_PERMISSION 权限之下。

    • S_RESOURCE 中针对Profile进行资源需求配置:MODE_ROLE / MODE_GROUP / MODE_TREE,确保用户最终计算出来的权限集在对应的Profile之中(参考:仅配置 MODE_ROLE = UNION)。

    • S_ACTION 中配置的 LEVEL 一定要大于 S_RESOURCE 中定义的操作级别。

  3. 将上述配置通过 Loader 导入系统中,重启容器生效(后续使用管理端则不需要重启)。

操作级别的内部定义:

符号 含义

0

READONLY

只读级别,最小等级。

1

READ

标准读取,通常是单记录读取。

2

READ_INTEGRATION

集成读取,通常会开放接口读取。

3

READ_EXPORT

从数据库中批量导出时所需。

4

ADD

创建和批量创建级别。

5

ADD_IMPORT

导入专用级别。

6

ADD_INTEGRATION

集成创建,通常会开放接口创建。

7

ADD_META

元数据创建接口,创建配置专用。

8

EDIT_APPROVE

审批确认更新接口,部分字段更新。

9

EDIT

标准编辑,更新和批量更新级别。

10

EDIT_INTEGRATION

集成更新,通常开放接口实现更新逻辑。

11

EDIT_META

元数据更新接口,更新配置专用。

12

DELETE

标准删除,删除和批量删除级别。

13

DELETE_PURGE

清除专用,通常用于数据清空。

14

DELETE_META

元数据删除专用,删除配置专用。

15

FULL

最高操作级别。

2. 视图配置

一般静态视图配置都是配置角色级别的视图相关信息,通常不会配置用户级视图。

  1. 找到权限基础配置中 S_RESOURCE 的主键,填充到 S_VIEW 中的 RESOURCE_ID 字段中。

  2. 没有特殊说明 NAMEPOSITION 都可以直接配置成 DEFAULT 默认值。

  3. OWNER_TYPE 中配置 ROLE,然后找到登录账号对应的角色ID,填充到 OWNER 中。

  4. 配置视图的三个核心维度:ROWS, PROJECTION, CRITERIA

  5. 将上述配置的 S_VIEW 记录导入到系统中,重启容器生效(后续使用管理端则不需要重启)。

3. 访问者配置

如果牵涉到访问者配置,则遵循如下流程执行:

  1. 将资源定义 S_RESOURCE 中的资源设置成 虚拟资源VIRTUAL = true)。

  2. 为虚拟资源配置 访问者语法,两种配置模式:

    • 直接配置 SEEK_SYNTAX 字段生成标准化的访问者语法模块。

    • 配置 SEEK_CONFIG / SEEK_COMPONENT 字段生成扩展的访问者语法模块。

  3. 配置好之后在根据所需要为资源配置访问者信息:S_VISITANT 表中追加相关记录。

3.7.1.6.示例/格式
S_VIEW 中的 PROJECTION

S_VIEW 表结构中的 PROJECTION 通常是 JsonArray 结构,如:

[
    "key",
    "name",
    "code",
    "createdAt",
    "createdBy"
]

上述配置会包含两个含义:

  • 在读取资源过程中使用了该视图之后,只会读取 五列 信息。

  • 读取的 五列 会按照配置顺序读取,即列表中的列顺序为:key, name, code, createdAt, createdBy

S_VIEW 中的 CRITERIA

S_VIEW 表结构中的 CRITERIA 通常是 JsonObject 结构,直接追加查询引擎语法,最终条件和请求条件按 AND 操作符合并。

S_VIEW 中的 ROWS

S_ROWS 表结构中的 ROWS 是一个 JsonObject 结构,会生成 IN 查询条件,如:

{
    "name" : [ "zero.desktop", "zero.desktop.my", "zero.desktop.my.todo-pending"],
    "code" : [ "A", "B" ]
}

您可以将上述配置片段理解成 QR 查询引擎的片段语法,如上述代码中如果 name, code 对应字段为 NAME, CODE,则会生成查询条件:

WHERE NAME IN ('zero.destkop', 'zero.desktop.my', 'zero.desktop.my.todo-pending') OR CODE IN ('A', 'B')

根据 QR 查询引擎规范,您可以按下边配置将中间连接符配置成 AND

{
    "name": ["..."],
    "code": ["..."],
    "": true
}
S_RESOURCE 中的 SEEK_SYNTAX

SEEK_SYNTAX 通常格式如下:

{
    "phase": "AFTER",
    "data": {
        "type": "RECORD",
        "viewId": "`${viewId}`",
        "identifier": "psi.pos"
    }
}

上述代码是一个单维度访问者代码,data`节点会包含一个 `type 属性,它和 phase 会构成不同的值处理模式,此处若您的数据来自于输入数据,则可使用 "`" 符号执行 JEXL 转换,如上述数据就会根据 type, viewId, identifier 计算访问者的 SEEK_KEY 来提取访问者信息。

上述配置中的 phasedata 节点中的 type 最终会生成如下格式的合法值:

phase type 含义

BEFORE_PROJECTION

BEFORE

PROJECTION

前置操作,修改查询引擎参数 projection。

BEFORE_CRITERIA

BEFORE

CRITERIA

前置操作,修改IQ讯引擎参数 criteria。

AFTER_RECORD

AFTER

RECORD

后置操作,执行记录处理,单数据增删改的记录过滤访问者计算。

AFTER_ROWS

AFTER

ROWS

后置操作,多记录,执行行筛选,筛选出集合数据中的某些行数据访问者计算。

AFTER_COLLECTION

AFTER

COLLECTION

后置操作,多记录,执行列筛选,过滤部分属性。

SEEK_SYNTAX 的多维格式如下:

{
    "phase": "AFTER",
    "selector": "io.vertx.tp.optic.secure.ConfineKind",
    "kind": "type",
    "data": {
        "ATOM": "`${identifier}/${control}`",
        "FLOW": "`${workflow}/${node}/DEFAULT`"
    }
}

上述代码段中,访问者调用中会使用 io.vertx.tp.optic.secure.ConfineKind 完成访问者选择,选择过程根据输入参数 type 值执行计算,由于使用了组件,所以执行过程可忽略 data 节点的数据信息,最终:

  • type = ATOM 会生成访问者的 SEEK_KEY = <模型标识符>/<控件ID>,动态建模可用。

  • type = FLOW 会生成访问者的 SEEK_KEY = <工作流名称>/<节点名称>/DEFAULT,流程引擎专用。

您也可以定义自己的 Confine 接口组件配置 selector 完成多维数据访问者扩展。Confine接口的定义如下:

public interface Confine {
    Future<JsonObject> restrict(JsonObject request, JsonObject syntax);
}

其中此处的 syntax 就是 SEEK_SYNTAX 配置的内容,而 request 参数对应到前文中 data 节点可使用的预设参数(预设参数不包含您在 Http 请求体 Body 中传入的参数),预设参数如下:

参数名 默认值 含义

appId

多应用模式中,所属应用程序ID,来自请求头:X-App-Id

appKey

所属应用程序的Key,敏感数据访问专用,来自请求头:X-App-Key

sigma

统一标识符,来自请求头:X-Sigma

language

多语言模式中,当前应用的语言信息,来自请求头:X-Lang

tenantId

多租户模式中,所属租户的ID,来自请求头:X-Tenant-Id

resourceId

当前请求正在访问的资源信息。

viewId

当前资源访问者所属视图ID。

view

DEFAULT

当前资源访问者访问的视图名称,默认 DEFAULT

position

DEFAULT

当前资源访问者访问的视图位置,默认 DEFAULT

S_VISITANT 中的 ACL_

由于访问者表中的 DM_ 字段格式和 S_VIEW 中保持一致,此处不再重复讲解。

  • DM_ROW 对应 ROWS

  • DM_QR 对应 CRITERIA

  • DM_COLUMN 对应 PROJECTION

最后简单讲解下 ACL_ 表单级语法:

字段 类型 格式和含义

ACL_VERGE

JsonObject

一般格式是:field = JsonObject 结构,包含依赖字段名和依赖字段相关配置。

ACL_VISIBLE

JsonArray

直接是 [field1, field2, …​] 结构,包含了可见性字段集,最终会转换成 Set。

ACL_VIEW

JsonArray

直接 [field1, field2, …​] 结构,包含了只读字段集,最终会转换成 Set。

ACL_VARIETY

JsonObject

Json格式,用于构造 AclView/AclMap专用,执行类型为 DATA。

ACL_VOW

JsonObject

Json格式,用于构造 AclView/AclMap专用,执行类型为 REFERENCE。

最后谈谈响应格式中的的 __acl 字段,这是Zero标准数据规范中的一部分,如果某个接口(包括JsonArray返回的接口)中包含了 __acl 则证明该接口包含ACL控制信息,它的格式如下:

{
    "access": [],
    "edition": [],
    "fields": []
}

上述格式是初期版本格式,代表:

  • fields:当前表单、列表能访问的所有属性集合。

  • access:您可以访问的所有属性集合。

  • edition:您可以编辑的所有属性集合。

3.7.2.管理端

前文讨论了Zero权限框架中的 消费端,本章节开始讨论权限框架的 管理端,管理端的主要目的是为您定制所需要的核心配置数据,并且通过交互式界面直接为用户执行权限、视图、访问者的授权工作,管理端入口在Zero Extension框架中有三个:

  • 角色管理 → 权限设置

  • 权限设置 → 角色授权 → 选择某个角色

  • 权限设置 → 用户特权(现阶段版本还未放出来)

管理端的主体是 角色,所以管理端的所有操作都是角色级的,只是在设计过程中管理端遵循如下设计原则: